Comment ajouter une authentification à votre application avec Flask-Login

introduction

Permettre aux utilisateurs de se connecter à votre application est l'une des fonctionnalités les plus courantes que vous ajouterez à votre application Web. Cet article explique comment ajouter une authentification à votre application Flask avec le package Flask-Login .

gif de l'application Flask et de la boîte de connexiongif de l'application Flask et de la boîte de connexion
 

Nous allons créer des pages d'inscription et de connexion qui permettent aux utilisateurs de se connecter et d'accéder à des pages protégées que les utilisateurs non connectés ne peuvent pas voir. Nous récupérons les informations du modèle utilisateur et les affichons sur nos pages protégées lorsque l'utilisateur se connecte pour simuler à quoi ressemblerait un profil.

Nous couvrirons les points suivants dans cet article:

Configuration de l'application

Notre application utilisera le modèle d'usine de l'application Flask avec des plans. Nous aurons un plan qui gérera tout ce qui est lié à l'authentification, et nous aurons un autre plan pour nos itinéraires réguliers, qui incluent l'index et la page de profil protégée. Dans une vraie application, bien sûr, vous pouvez décomposer les fonctionnalités comme vous le souhaitez, mais ce que j'ai proposé fonctionnera bien pour ce tutoriel.

Pour commencer, nous devons créer les répertoires et fichiers de notre projet:

- project

---- templates

-------- base.html <!-- contains common layout and links -->

-------- index.html <!-- show the home page -->

-------- login.html <!-- show the login form -->

-------- profile.html <!-- show the profile page -->

-------- signup.html <!-- show the signup form -->

---- __init__.py <!-- setup our app -->

---- auth.py <!-- the auth routes for our app -->

---- main.py <!-- the non-auth routes for our app -->

---- models.py <!-- our user model -->

Vous pouvez créer ces fichiers et nous les ajouterons au fur et à mesure de notre progression.

Installation de packages

Il y a trois packages principaux dont nous avons besoin pour notre projet:

Nous utiliserons SQLite pour éviter d'avoir à installer des dépendances supplémentaires pour la base de données. Voici ce que vous devez exécuter après avoir créé votre environnement virtuel pour installer les packages.

pip install flask flask-sqlalchemy flask-login

Création du fichier d'application principal

Commençons par créer le fichier __init__.py pour notre projet. Cela aura la fonction de créer notre application qui initialisera la base de données et enregistrera nos plans. Pour le moment, cela ne fera pas grand-chose, mais cela sera nécessaire pour le reste de notre application. Nous devons initialiser SQLAlchemy, définir certaines valeurs de configuration et enregistrer nos plans ici.

__init__.py

from flask import Flask__

from flask_sqlalchemy import SQLAlchemy

 

# init SQLAlchemy so we can use it later in our models

db = SQLAlchemy()

 

def create_app():

    app = Flask(__name__)

 

    app.config['SECRET_KEY'] = '9OLWxND4o83j4K4iuopO'

    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'

 

    db.init_app(app)

 

    # blueprint for auth routes in our app

    from .auth import auth as auth_blueprint

    app.register_blueprint(auth_blueprint)

 

    # blueprint for non-auth parts of app

    from .main import main as main_blueprint

    app.register_blueprint(main_blueprint)

 

    return app

Ajout d'itinéraires

Maintenant que nous avons le fichier d'application principal, nous pouvons commencer à ajouter nos itinéraires.

Pour nos itinéraires, nous utiliserons deux plans. Pour notre plan principal, nous aurons une page d'accueil (/) et une page de profil (/ profile) après la connexion. Si l'utilisateur essaie d'accéder à la page de profil sans être connecté, il sera envoyé à notre connexion route.

Pour notre plan d'authentification, nous aurons des itinéraires pour récupérer à la fois la page de connexion (/ login) et la page d'inscription (/ signup). Nous aurons également des itinéraires pour gérer la demande POST à ​​partir de ces deux itinéraires. Enfin, nous aurons une route de déconnexion (/ logout) pour déconnecter un utilisateur actif.

Allons de l'avant et ajoutons-les même s'ils ne feront pas grand-chose. Plus tard, nous les mettrons à jour pour pouvoir les utiliser.

main.py

from flask import Blueprint

from . import db

 

main = Blueprint('main', __name__)

 

@main.route('/')

def index():

    return 'Index'

 

@main.route('/profile')

def profile():

    return 'Profile'

auth.py

from flask import Blueprint

from . import db

 

auth = Blueprint('auth', __name__)

 

@auth.route('/login')

def login():

    return 'Login'

 

@auth.route('/signup')

def signup():

    return 'Signup'

 

@auth.route('/logout')

def logout():

    return 'Logout'

Vous pouvez maintenant définir les valeurs FLASK_APP et FLASK_DEBUG et exécuter le projet. Vous devriez pouvoir voir naviguer vers les cinq URL possibles et voir le texte renvoyé.

export FLASK_APP=project

export FLASK_DEBUG=1

flask run

 

Création de modèles

Allons de l'avant et créons les modèles utilisés dans notre application. Il s'agit de la première étape avant de pouvoir implémenter la fonctionnalité de connexion réelle. Notre application utilisera quatre modèles:

Nous aurons également un modèle de base qui aura un code commun à chacune des pages. Dans ce cas, le modèle de base aura des liens de navigation et la disposition générale de la page. Créons-les maintenant:

templates / base.html

<!DOCTYPE html>

<html>

 

<head>

    <meta charset="utf-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Flask Auth Example</title>

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css" />

</head>

 

<body>

    <section class="hero is-primary is-fullheight">

 

        <div class="hero-head">

            <nav class="navbar">

                <div class="container">

 

                    <div id="navbarMenuHeroA" class="navbar-menu">

                        <div class="navbar-end">

                            <a href="{{ url_for('main.index') }}" class="navbar-item">

                                Home

                            </a>

                            <a href="{{ url_for('main.profile') }}" class="navbar-item">

                                Profile

                            </a>

                            <a href="{{ url_for('auth.login') }}" class="navbar-item">

                                Login

                            </a>

                            <a href="{{ url_for('auth.signup') }}" class="navbar-item">

                                Sign Up

                            </a>

                            <a href="{{ url_for('auth.logout') }}" class="navbar-item">

                                Logout

                            </a>

                        </div>

                    </div>

                </div>

            </nav>

        </div>

 

        <div class="hero-body">

            <div class="container has-text-centered">

               {% block content %}

               {% endblock %}

            </div>

        </div>

    </section>

</body>

 

</html>

templates / index.html

{% extends "base.html" %}

 

{% block content %}

<h1 class="title">

  Flask Login Example

</h1>

<h2 class="subtitle">

  Easy authentication and authorization in Flask.

</h2>

{% endblock %}

templates / login.html

{% extends "base.html" %}

 

{% block content %}

<div class="column is-4 is-offset-4">

    <h3 class="title">Login</h3>

    <div class="box">

        <form method="POST" action="/login">

            <div class="field">

                <div class="control">

                    <input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus="">

                </div>

            </div>

 

            <div class="field">

                <div class="control">

                    <input class="input is-large" type="password" name="password" placeholder="Your Password">

                </div>

            </div>

            <div class="field">

                <label class="checkbox">

                    <input type="checkbox">

                    Remember me

                </label>

            </div>

            <button class="button is-block is-info is-large is-fullwidth">Login</button>

        </form>

    </div>

</div>

{% endblock %}

templates / signup.html

{% extends "base.html" %}

 

{% block content %}

<div class="column is-4 is-offset-4">

    <h3 class="title">Sign Up</h3>

    <div class="box">

        <form method="POST" action="/signup">

            <div class="field">

                <div class="control">

                    <input class="input is-large" type="email" name="email" placeholder="Email" autofocus="">

                </div>

            </div>

 

            <div class="field">

                <div class="control">

                    <input class="input is-large" type="text" name="name" placeholder="Name" autofocus="">

                </div>

            </div>

 

            <div class="field">

                <div class="control">

                    <input class="input is-large" type="password" name="password" placeholder="Password">

                </div>

            </div>

 

            <button class="button is-block is-info is-large is-fullwidth">Sign Up</button>

        </form>

    </div>

</div>

{% endblock %}

templates / profile.html

{% extends "base.html" %}

 

{% block content %}

<h1 class="title">

  Welcome, Anthony!

</h1>

{% endblock %}

Une fois que vous avez ajouté les modèles, nous pouvons mettre à jour les instructions de retour dans chacun des itinéraires que nous devons renvoyer les modèles au lieu du texte:

main.py

from flask import Blueprint, render_template

...

@main.route('/')

def index():

    return render_template('index.html')

 

@main.route('/profile')

def profile():

    return render_template('profile.html')

auth.py

from flask import Blueprint, render_template

...

@auth.route('/login')

def login():

    return render_template('login.html')

 

@auth.route('/signup')

def signup():

    return render_template('signup.html')

Par exemple, voici à quoi ressemble la page d'inscription si vous accédez à / signup. Vous devriez également pouvoir voir les pages pour /, / login et / profile. Nous allons laisser / se déconnecter pour l'instant car il n'affichera pas de modèle une fois terminé.

page d'inscription sur / signuppage d'inscription sur / signup
 

Création de modèles utilisateur

Notre modèle d'utilisateur représente ce que signifie pour notre application d'avoir un utilisateur. Nous aurons des champs pour une adresse e-mail, un mot de passe et un nom. Bien sûr, dans votre application, vous pouvez décider que vous souhaitez stocker davantage d'informations par utilisateur. Vous pouvez ajouter des éléments comme l'anniversaire, la photo de profil, l'emplacement ou toute préférence utilisateur.

Les modèles créés dans Flask-SQLAlchemy sont représentés par des classes qui se traduisent ensuite en tables dans une base de données. Les attributs de ces classes se transforment alors en colonnes pour ces tables.

Allons de l'avant et créons ce modèle utilisateur:

models.py

from . import db

 

class User(db.Model):

    id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy

    email = db.Column(db.String(100), unique=True)

    password = db.Column(db.String(100))

    name = db.Column(db.String(1000))

Configuration de la base de données

Like I said before, we’ll be using a SQLite database. We could create a SQLite database on our own, but let’s have Flask-SQLAlchemy do it for us. We already have the path of the database specified in the __init__.py file, so we just need to tell Flask-SQLAlchemy to create the database for us in the Python REPL.

If you stop your app and open up a Python REPL, we can create the database using the create_all method on the db object.

from project import db, create_app

db.create_all(app=create_app()) # pass the create_app result so Flask-SQLAlchemy gets the configuration.

You will now see a db.sqlite file in your project directory. This database will have our user table in it.

Setting Up the Authorization Function

Pour notre fonction d'inscription, nous allons prendre les données saisies par l'utilisateur dans le formulaire et les ajouter à notre base de données. Mais avant de l'ajouter, nous devons nous assurer que l'utilisateur n'existe pas déjà dans la base de données. Si ce n'est pas le cas, nous devons nous assurer de hacher le mot de passe avant de le placer dans la base de données, car nous ne voulons pas que nos mots de passe soient stockés en texte brut.

Commençons par ajouter une deuxième fonction pour gérer les données du formulaire POSTed. Dans cette fonction, nous rassemblerons d'abord les données transmises par l'utilisateur.

Commençons par créer la fonction et ajouter une redirection vers le bas car nous savons que lorsque nous ajoutons l'utilisateur à la base de données, nous redirigerons vers la route de connexion.

auth.py

from flask import Blueprint, render_template, redirect, url_for

...

@auth.route('/signup', methods=['POST'])

def signup_post():

    # code to validate and add user to database goes here

    return redirect(url_for('auth.login'))

Maintenant, ajoutons le reste du code nécessaire pour inscrire un utilisateur.

Pour commencer, nous devrons utiliser l'objet request pour obtenir les données du formulaire:

auth.py

from flask import Blueprint, render_template, redirect, url_for, request

from werkzeug.security import generate_password_hash, check_password_hash

from .models import User

from . import db

...

@auth.route('/signup', methods=['POST'])

def signup_post():

    email = request.form.get('email')

    name = request.form.get('name')

    password = request.form.get('password')

 

    user = User.query.filter_by(email=email).first() # if this returns a user, then the email already exists in database

 

    if user: # if a user is found, we want to redirect back to signup page so user can try again

        return redirect(url_for('auth.signup'))

 

    # create new user with the form data. Hash the password so plaintext version isn't saved.

    new_user = User(email=email, name=name, password=generate_password_hash(password, method='sha256'))

 

    # add the new user to the database

    db.session.add(new_user)

    db.session.commit()

 

    return redirect(url_for('auth.login'))

Test de la méthode d'inscription

Maintenant que la méthode d'inscription est terminée, nous devrions pouvoir créer un nouvel utilisateur. Utilisez le formulaire pour créer un utilisateur.

Il y a deux façons de vérifier si l'inscription a fonctionné: vous pouvez utiliser un visualiseur de base de données pour regarder la ligne qui a été ajoutée à votre table, ou vous pouvez simplement essayer de vous inscrire à nouveau avec la même adresse e-mail, et si vous obtenez un erreur, vous savez que le premier e-mail a été enregistré correctement. Prenons donc cette approche.

Nous pouvons ajouter du code pour faire savoir à l'utilisateur que l'e-mail existe déjà et lui dire d'aller sur la page de connexion. En appelant la fonction flash, nous enverrons un message à la prochaine requête, qui dans ce cas, est la redirection. La page sur laquelle nous atterrirons aura alors accès à ce message dans le modèle.

Tout d'abord, nous ajoutons le flash avant de rediriger vers notre page d'inscription.

auth.py

from flask import Blueprint, render_template, redirect, url_for, request, flash

...

@auth.route('/signup', methods=['POST'])

def signup_post():

    ...

    if user: # if a user is found, we want to redirect back to signup page so user can try again

        flash('Email address already exists')

        return redirect(url_for('auth.signup'))

Pour obtenir le message flashé dans le modèle, nous pouvons ajouter ce code au-dessus du formulaire. Cela affichera le message directement au-dessus du formulaire.

templates / signup.html

...

{% with messages = get_flashed_messages() %}

{% if messages %}

    <div class="notification is-danger">

        {{ messages[0] }}. Go to <a href="{{ url_for('auth.login') }}">login page</a>.

    </div>

{% endif %}

{% endwith %}

<form method="POST" action="/signup">

...

Boîte d'inscription affichant un message indiquant que "l'adresse e-mail existe déjà. Aller à la page de connexion" dans une boîte rose foncéBoîte d'inscription affichant un message indiquant que "l'adresse e-mail existe déjà. Aller à la page de connexion" dans une boîte rose foncé
 

Ajout de la méthode de connexion

La méthode de connexion est similaire à la fonction d'inscription dans la mesure où nous prendrons les informations utilisateur et ferons quelque chose avec. Dans ce cas, nous comparerons l'adresse e-mail saisie pour voir si elle se trouve dans la base de données. Si c'est le cas, nous testerons le mot de passe fourni par l'utilisateur en hachant le mot de passe que l'utilisateur transmet et en le comparant au mot de passe haché dans la base de données. Nous savons que l'utilisateur a entré le mot de passe correct lorsque les deux mots de passe hachés correspondent.

Une fois que l'utilisateur a réussi la vérification du mot de passe, nous savons qu'il possède les informations d'identification correctes et nous pouvons continuer et les connecter à l'aide de Flask-Login. En appelant login_user, Flask-Login créera une session pour cet utilisateur qui persistera pendant que l'utilisateur reste connecté, ce qui permettra à l'utilisateur d'afficher les pages protégées.

Nous pouvons commencer avec une nouvelle route pour gérer les données POSTées. Nous redirigeons vers la page de profil lorsque l'utilisateur se connecte avec succès:

auth.py

...

@auth.route('/login', methods=['POST'])

def login_post():

    # login code goes here

    return redirect(url_for('main.profile'))

Maintenant, nous devons vérifier si l'utilisateur dispose des informations d'identification correctes:

auth.py

...

@auth.route('/login', methods=['POST'])

def login_post():

    email = request.form.get('email')

    password = request.form.get('password')

    remember = True if request.form.get('remember') else False

 

    user = User.query.filter_by(email=email).first()

 

    # check if user actually exists

    # take the user supplied password, hash it, and compare it to the hashed password in database

    if not user or not check_password_hash(user.password, password):

        flash('Please check your login details and try again.')

        return redirect(url_for('auth.login')) # if user doesn't exist or password is wrong, reload the page

 

    # if the above check passes, then we know the user has the right credentials

    return redirect(url_for('main.profile'))

Ajoutons le bloc dans le modèle pour que l'utilisateur puisse voir le message flashé. Comme le formulaire d'inscription, ajoutons le message d'erreur potentiel directement au-dessus du formulaire:

templates / login.html

...

{% with messages = get_flashed_messages() %}

{% if messages %}

    <div class="notification is-danger">

        {{ messages[0] }}

    </div>

{% endif %}

{% endwith %}

<form method="POST" action="/login">

Nous avons donc la possibilité de dire qu'un utilisateur a été connecté avec succès, mais il n'y a rien pour réellement connecter l'utilisateur n'importe où. C'est là que nous apportons Flask-Login.

Mais d'abord, nous avons besoin de quelques éléments pour que Flask-Login fonctionne.

Nous commençons par ajouter quelque chose appelé le UserMixinà notre modèle d'utilisateur. Le UserMixinajoutera des attributs Flask-Login à notre modèle afin que Flask-Login puisse travailler avec lui.

models.py

from flask_login import UserMixin

from . import db

 

class User(UserMixin, db.Model):

    id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy

    email = db.Column(db.String(100), unique=True)

    password = db.Column(db.String(100))

    name = db.Column(db.String(1000))

Ensuite, nous devons spécifier notre chargeur d'utilisateur. Un chargeur d'utilisateurs indique à Flask-Login comment trouver un utilisateur spécifique à partir de l'ID stocké dans son cookie de session. Nous pouvons ajouter ceci dans notre create_appfonction avec le initcode pour Flask-Login:

__init__.py

...

from flask_login import LoginManager

 

def create_app():

    ...

    db.init_app(app)

 

    login_manager = LoginManager()

    login_manager.login_view = 'auth.login'

    login_manager.init_app(app)

 

    from .models import User

 

    @login_manager.user_loader

    def load_user(user_id):

        # since the user_id is just the primary key of our user table, use it in the query for the user

        return User.query.get(int(user_id))

Enfin, nous pouvons ajouter la login_userfonction juste avant de rediriger vers la page de profil pour créer la session:

auth.py

from flask_login import login_user

from .models import User

...

@auth.route('/login', methods=['POST'])

def login_post():

    # if the above check passes, then we know the user has the right credentials

    login_user(user, remember=remember)

    return redirect(url_for('main.profile'))

Avec la configuration de Flask-Login, nous pouvons utiliser la route / login.

Lorsque tout est réussi, nous verrons la page de profil.

Page de profil avec "Bienvenue, Anthony!"Page de profil avec "Bienvenue, Anthony!"
 

Protection des pages

Si votre nom n'est pas aussi Anthony, alors vous verrez que votre nom est faux. Ce que nous voulons, c'est que le profil affiche le nom dans la base de données. Donc, tout d'abord, nous devons protéger la page, puis accéder aux données de l'utilisateur pour obtenir le nom.

Pour protéger une page lors de l'utilisation de Flask-Login: nous ajoutons le @login_requrieddécorateur entre l'itinéraire et la fonction. Cela empêchera un utilisateur non connecté de voir l'itinéraire. Si l'utilisateur n'est pas connecté, l'utilisateur sera redirigé vers la page de connexion, selon la configuration de Flask-Login.

Avec les itinéraires décorés avec le @login_requireddécorateur, nous avons alors la possibilité d'utiliser l' current_userobjet à l'intérieur de la fonction. Cela current_userreprésente l'utilisateur de la base de données, et nous pouvons accéder à tous les attributs de cet utilisateur avec la notation par points. Par exemple, current_user.email, current_user.passwordet current_user.name, et current_user.idretourne les valeurs réelles stockées dans la base de données pour l'utilisateur connecté.

Utilisons le nom de l'utilisateur actuel et envoyons-le au modèle. Nous utiliserons ensuite ce nom et afficherons sa valeur.

main.py

from flask import Blueprint, render_template

from flask_login import login_required, current_user

...

@main.route('/profile')

@login_required

def profile():

    return render_template('profile.html', name=current_user.name)

templates / profile.html

...

<h1 class="title">

  Welcome, {{ name }}!

</h1>

Une fois que nous allons sur notre page de profil, nous voyons alors que le nom de l'utilisateur apparaît.

Page d'accueil de l'utilisateur avec le nom de l'utilisateur actuellement connectéPage d'accueil de l'utilisateur avec le nom de l'utilisateur actuellement connecté
 

La dernière chose que nous pouvons faire est de mettre à jour notre vue de déconnexion. Nous pouvons appeler la logout_userfonction dans un itinéraire de déconnexion. Nous avons le @login_requireddécorateur car cela n'a pas de sens de déconnecter un utilisateur qui n'est pas connecté pour commencer.

auth.py

from flask_login import login_user, logout_user, login_required

...

@auth.route('/logout')

@login_required

def logout():

    logout_user()

    return redirect(url_for('main.index'))

Après nous être déconnectés et réessayer de visualiser la page de profil, un message d'erreur apparaît. En effet, Flask-Login clignote un message pour nous lorsque l'utilisateur n'est pas autorisé à accéder à une page.

Page de connexion avec message indiquant que l'utilisateur doit se connecter pour accéder à la pagePage de connexion avec message indiquant que l'utilisateur doit se connecter pour accéder à la page
 

Une dernière chose que nous pouvons faire est de mettre des ifinstructions dans les modèles pour afficher uniquement les liens pertinents pour l'utilisateur. Ainsi, avant que l'utilisateur ne se connecte, il aura la possibilité de se connecter ou de s'inscrire. Une fois connecté, il peut accéder à son profil ou se déconnecter:

templates / base.html

...

<div class="navbar-end">

    <a href="{{ url_for('main.index') }}" class="navbar-item">

        Home

    </a>

    {% if current_user.is_authenticated %}

    <a href="{{ url_for('main.profile') }}" class="navbar-item">

        Profile

    </a>

    {% endif %}

    {% if not current_user.is_authenticated %}

    <a href="{{ url_for('auth.login') }}" class="navbar-item">

        Login

    </a>

    <a href="{{ url_for('auth.signup') }}" class="navbar-item">

        Sign Up

    </a>

    {% endif %}

    {% if current_user.is_authenticated %}

    <a href="{{ url_for('auth.logout') }}" class="navbar-item">

        Logout

    </a>

    {% endif %}

</div>

page d'accueil avec la navigation Accueil, Connexion et Inscription en haut de l'écranpage d'accueil avec la navigation Accueil, Connexion et Inscription en haut de l'écran
 

Conclusion

Nous avons utilisé Flask-Login et Flask-SQLAlchemy pour créer un système de connexion pour notre application. Nous avons expliqué comment authentifier un utilisateur en créant d'abord un modèle utilisateur et en stockant les informations utilisateur. Ensuite, nous avons dû vérifier que le mot de passe de l'utilisateur était correct en hachant le mot de passe du formulaire et en le comparant à celui stocké dans la base de données. Enfin, nous avons ajouté une autorisation à notre application en utilisant le @login_requireddécorateur sur une page de profil afin que seuls les utilisateurs connectés puissent voir cette page.

Ce que nous avons créé dans ce didacticiel sera suffisant pour les petites applications, mais si vous souhaitez avoir plus de fonctionnalités depuis le début, vous pouvez envisager d'utiliser les bibliothèques Flask-User ou Flask-Security, qui sont toutes deux construites au-dessus de la Bibliothèque Flask-Login.